package org.tau.workshop2011.semantics

import scala.collection.mutable.{Map => MutableMap, HashMap => MutableHashMap}
import scala.collection.mutable.{Set => MutableSet, HashSet => MutableHashSet}
import org.tau.workshop2011.expressions.Type
import org.tau.workshop2011.parsing.AST.{InitialAttribute, Attribute => ASTAttribute, Variable => ASTVariable}

class ResolvedVariable(val definition: ASTAttribute, val globalName: String) {

  def resolveTypeTo(newType: Type) {
    definition.resolveTypeTo(newType)
  }

  def getVariable = definition.leftSide

  override def toString = {
    definition.getName +
      (if (globalName != definition.getName) "(" + globalName + ")" else "") +
      ":" + definition.getLeftType
  }
}

object ResolvedVariable {
  def from(node: ASTVariable): ResolvedVariable = {
    node.environment.get(node)
  }
  def from(node: ASTAttribute): ResolvedVariable = {
    ResolvedVariable from node.leftSide
  }

  implicit def resolved2ast(rv: ResolvedVariable): ASTVariable = rv.getVariable
  implicit def ast2resolved(vast: ASTVariable): ResolvedVariable = ResolvedVariable from vast
}

abstract class SymbolTable(val parent: SymbolTable) {
  val localMapping: MutableMap[String, ResolvedVariable] = new MutableHashMap[String, ResolvedVariable]

  def define(attribute: ASTAttribute): ResolvedVariable = {
    val localName: String = attribute.getName
    if (localMapping contains localName)
      throw new DuplicateDefineException(localName)
    else {
      attribute.environment = this
      attribute.leftSide.environment = this
      val rv = new ResolvedVariable(attribute, uniqueGlobalName(localName))
      localMapping += localName -> rv
      globalDefine (rv)
      rv
    }
  }

  def get(variable: ASTVariable, expectedType: Type = Type.tUnknown): ResolvedVariable = {
    if (variable.functionName)
      return encounterFunction(variable)
    val localName = variable.id
    if (localMapping contains localName) {
      val rv = localMapping(localName)
      if (expectedType != Type.tUnknown)
      /* This will throw an error if the resolving goes wrong */
        rv.resolveTypeTo(expectedType)
      rv
    }
    else if (parent != null) {
      parent get(variable, expectedType)
    }
    else
      throw new UseBeforeDefineException(localName)
  }

  def has(localName: String): Boolean = {
    if (localMapping contains localName) {
      true
    }
    else if (parent != null) {
      parent has (localName)
    }
    else
      false
  }

  def globalDefine (rv: ResolvedVariable)
  def encounterFunction (func: ASTVariable): ResolvedVariable
  def getGlobalSymbolTable : GlobalSymbolTable

  def uniqueGlobalName(localName: String): String
}

class LocalSymbolTable(parent: SymbolTable)
  extends SymbolTable(parent) {
  override def uniqueGlobalName(localName: String) = parent.uniqueGlobalName(localName)
  override def getGlobalSymbolTable : GlobalSymbolTable = parent.getGlobalSymbolTable
  override def globalDefine (rv: ResolvedVariable) { parent.globalDefine(rv) }
  override def encounterFunction (func: ASTVariable): ResolvedVariable = { parent.encounterFunction(func) }

}

class GlobalSymbolTable extends SymbolTable(null) {

  var globalNames: MutableSet[String] = new MutableHashSet[String]
  var countDefinitions = 0;

  override def uniqueGlobalName(localName: String): String = {
    var resultName: String = "g_" + localName
    if (globalNames contains resultName) {
      countDefinitions += 1
      resultName = "v" + countDefinitions + "_" + localName
    }

    globalNames += resultName
    resultName
  }

  def isGlobal(rv: ResolvedVariable): Boolean = {
    rv.globalName startsWith "g_"
  }

  override def getGlobalSymbolTable : GlobalSymbolTable = this

  override def globalDefine (rv: ResolvedVariable) {
    localMapping += rv.globalName -> rv
  }

  override def encounterFunction (func: ASTVariable): ResolvedVariable = {
    val globalFuncName = "func_" + func.id
    if (! (localMapping contains globalFuncName)) {
      localMapping += globalFuncName -> new ResolvedVariable(new InitialAttribute(func, None), globalFuncName)
      func.environment = this
    }
    localMapping(globalFuncName)
  }

  def globalGet (globalName: String) : ResolvedVariable = {
    localMapping(globalName)
  }

  val globalWidth = new InitialAttribute(new ASTVariable("width", Type.tInt), None)
  val globalHeight = new InitialAttribute(new ASTVariable("height", Type.tInt), None)

  define(globalWidth)
  define(globalHeight)
}